package com.demo.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.demo.mapper.StudentMapper;
import com.demo.util.DelayTaskConsumer;
import com.demo.util.Item;
import com.demo.util.Redis;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;

import javax.annotation.Resource;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@RestController
public class StudentController {

    Log log = LogFactory.getLog("三岁小仙仙");

    @Resource
    StudentMapper studentMapper;

    Jedis redis = Redis.getRedis();

    static DelayQueue<Item> queue = new DelayQueue<>();
    static ExecutorService executorService;

    static {
        executorService = Executors.newCachedThreadPool();
    }

    /**
     * 根据班级号，获得学生的考试信息
     *
     * @param classNumber 班级号
     * @return 数组
     * @throws ParseException 日期时间转换类异常
     */
    @PostMapping("/autoGetExaminationInfo")
    public synchronized String autoGetExaminationInfo(String classNumber, String studentNumber) throws ParseException {
        //        List<String> list = examinationRecordDao.findStudentExamination(className);
        List list = new ArrayList();
        if (redis.exists(classNumber)) {
            String jsonString = redis.get(classNumber);
            JSONArray jsonArray = JSONArray.parseArray(jsonString);
            for (int i = 0; i < jsonArray.size(); i++) {
                JSONObject jsonObject = JSONObject.parseObject(jsonArray.get(i).toString());
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
                Date endTime = sdf.parse(jsonObject.get("endTime").toString());
                // 判断该用户所在班级 是否 有考试科目 处于 待开考状态 并返回
                // 结束时间 大于 当前时间 表示 有待开考科目
                // 反之 于小 则表示 已经过了该科目的考试时间
                if (endTime.getTime() > new Date().getTime()) {
                    StringBuffer sb = new StringBuffer();
                    sb.append(jsonObject.get("coursesName")).append(",");
                    sb.append(jsonObject.get("startTime")).append(",");
                    sb.append(jsonObject.get("endTime"));
                    Date startTime = sdf.parse(jsonObject.get("startTime").toString());
                    // 判断 该科目的开始时间 是否 小于等于 当前时间
                    // 若开始时间 小于等于 当前时间 表示该科目已经开考 返回试题库号
                    if (startTime.getTime() <= new Date().getTime()) {
                        // 在为用户返回试题号前，判断一下用户是否已经完成答题了，这个情况，开发测试中
                        // 发现用户提交后，返回主页面，用户在考试结束前 依然可以拿到题，我们需要在这一步判断用户
                        // 是否已经作答过，进行进一个处理 根据班级号和试题库号 查询该答案记录表中是否有该记录作答记录
                        StringBuffer key = new StringBuffer();
                        key.append(classNumber);
                        key.append("-");
                        key.append(jsonObject.get("questionBank") != null?jsonObject.get("questionBank").toString():"null");
                        // 判断本班学生作答记录表是否存在
                        if (redis.exists(key.toString())) {
                            // 若存在 判断该用户是否已经作答
                            List<String> rsmap = redis.hmget(key.toString(), studentNumber);
                            if (rsmap.get(0) != null) {
                                sb.append(",").append("isAnswer");
                            } else {
                                sb.append(",").append(jsonObject.get("questionBank"));
                            }
                        } else {
                            sb.append(",");
                            sb.append(jsonObject.get("questionBank"));
                        }
                    } else {
                        sb.append(",").append("noExamination");
                    }
                    list.add(sb.toString());
                }
            }
        }
        return JSON.toJSONString(list);
    }


    /**
     * 根据班级号、试题库号（具有唯一性） 获取对应的考试试题信息
     *
     * @param classNumber  班级号
     * @param questionBank 试题库号
     * @return 数组对象
     * <p>
     * 进入该方法，表明已经有课程考试已经开考，用户拿着自己的班级号和试题库号，请求获取试题信息
     */
    @PostMapping("/autoGetExaminationQuestions")
    public String autoGetExaminationQuestions(String classNumber, String questionBank) {
        synchronized (this) {
            if (redis.exists(classNumber)) {
                // 获取字符串类型的json数组对象
                // 转换为json数组
                JSONArray jsonArray = JSONArray.parseArray(redis.get(classNumber));
                for (int i = 0; i < jsonArray.size(); i++) {
                    // 将json数组内的对象转换为java对象
                    JSONObject jsonObject = JSONObject.parseObject(jsonArray.get(i).toString());
                    // 循环缓存中记录的试题库号 是否 与用户上传的一样
                    //if(jsonObject.get("questionBank").toString().equals(questionBank)){}
                    // 判断该试题库还是否存在，因为试题库有过期时间，若有用户在考试结束后，刷新页面
                    // 前端将会继续拿着试题库号来获取试题，但，当下已经过了考试时间，试题库已经不存在了
                    // 所示是获取不了试题库的，需要给用户返回一个空数组，前端自动处理
                    // 若不加以判断处理，则服务端会出错报500
                    if (!redis.exists(questionBank)) {
                        return "[]";
                    }
                    ;
                    // 若存在，根据试题库号获取对应的所有试题信息
                    // 转换为json数组对象
                    JSONArray parseArray = JSONArray.parseArray(redis.get(questionBank));
                    for (int j = 0; j < parseArray.size(); j++) {
                        JSONObject object = JSONObject.parseObject(parseArray.get(j).toString());
                        // 将正确答案置空，并返回
                        object.put("rightOption", "");
                        parseArray.remove(j);
                        parseArray.add(j, object);
                    }
                    // 返回所有试题信息
                    return JSON.toJSONString(parseArray);
                }
            }
        }
        return "[]";
    }

    /**
     * 保存学生提交的作答信息
     *
     * @param data 作答数据
     * @return
     * @throws ParseException
     */
    @PostMapping("/submitExaminationInfo")
    public synchronized boolean submitExaminationInfo(String data) throws ParseException, InterruptedException {
        // 转换字符类型的json对象
        JSONObject jsonObject = JSONObject.parseObject(data);
        // 提取用户提交数据中的试题库号，判断该试题库是否存在
        // 若不存在，表示已经过了考试最后时间
        // 若存在，表示当前考试未结束
        if (!redis.exists(jsonObject.get("questionBank").toString())) {
            // 判断不存在，直接驳回用户提交作答信息，本科目做0分处理
            // 后面的代码段不再执行
            return false;
        }
        //将每位用户提交的答案进行缓存 创建一个Key 名为班级号-试题库
        StringBuffer key = new StringBuffer();
        key.append(jsonObject.getString("classNumber"))
                .append("-").append(jsonObject.getString("questionBank"));
        if (redis.hkeys(key.toString()).isEmpty()) {
            //有学生提交作答答案，判断redis是否有该学生所属的答案记录库，不存在则创建并记录，并添加到队列并开启延迟任务
            saveStudentAnswerToRedis(jsonObject, key.toString());
            String classNumber = redis.get(jsonObject.get("classNumber").toString());
            JSONArray jsonArray = JSONArray.parseArray(classNumber);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
            Date endTime = new Date();
            for (int i = 0; i < jsonArray.size(); i++) {
                JSONObject jsonObject1 = JSONObject.parseObject(jsonArray.getString(i));
                if (jsonObject1.getString("questionBank")!=null && jsonObject1.getString("questionBank").equals(jsonObject.getString("questionBank"))) {
                    endTime = sdf.parse(jsonObject1.getString("endTime"));
                    break;
                }
            }
            long l = Math.round((endTime.getTime() - new Date().getTime()) / 1000);
            //redis过期时间=原结束时间+10分钟(实际使用秒)
            redis.expire(key.toString(), (int) (l + 60 * 10));
            //数据写入DB的时间=原本结束时间+[0~5)分钟（实际使用秒）（能取0，但取不了5 最多4.999....）
            //Item item = new Item(key.toString(), l + Math.round(60 * (Math.random() * 5)), TimeUnit.SECONDS);
            Item item = new Item(key.toString(), l , TimeUnit.SECONDS);
            queue.put(item);
            executorService.execute(new DelayTaskConsumer(queue, executorService,
                    studentMapper, redis, key.toString()));

        } else {
            saveStudentAnswerToRedis(jsonObject, key.toString());
        }
        return true;
    }

    /**
     * 保存学生答案到redis
     *
     * @param jsonObject 答案
     * @param key        redis键，答案库
     */
    private void saveStudentAnswerToRedis(JSONObject jsonObject, String key) {
        Map<String, String> map = new HashMap<>();
        map.put(jsonObject.get("studentNumber").toString(), jsonObject.get("rightOption").toString());
        redis.hmset(key, map);
    }

    /**
     * 获取考试成绩
     *
     * @param classNumber   班级号
     * @param studentNumber 学号
     * @return
     */
    @PostMapping("/autoGetStudentExaminationRecord")
    public synchronized Object autoGetStudentExaminationRecord(String classNumber, String studentNumber) throws ParseException {
        Map map = new HashMap<>();
        map.put("classNumber", classNumber);
        map.put("studentNumber", studentNumber);
        Map<String, Object> classAllStudent = new HashMap<>();
        try {
            /**
             * 设置抛异常的意义：前端请求的classNumber可能不存在，需要处理返回空。
             */
            classAllStudent = studentMapper.findClassAllStudent(map);
        } catch (Exception e) {
            return null;
        }

        /**
         * 下列判断的意义：前端请求的studentNumber可能不存在
         * 否则，在map为空时，再执行remove操作，会报错
         *
         */
        if (classAllStudent == null) return null;

        /**
         * 移除学生基本信息，用不上
         */
        classAllStudent.remove("studentName");
        classAllStudent.remove("studentNumber");
        classAllStudent.remove("studentPass");
        classAllStudent.remove("studentEmail");

        /**
         * 获取学生的科目
         */
        Set<String> strings = classAllStudent.keySet();
        List<Map<String, Object>> classCourseExaminationTime = new ArrayList<>();
        if (strings.iterator().hasNext()) {
            /**
             * 根据班级号和科目，查询信息科目的信息
             */
            classCourseExaminationTime = studentMapper.findClassCourseExaminationTime(classNumber, strings);
        }
        if (!classCourseExaminationTime.isEmpty()) {
            Iterator<Map<String, Object>> iterator = classCourseExaminationTime.iterator();
            while (iterator.hasNext()) {
                Map<String, Object> next = iterator.next();
                /**
                 * 下列判断执行意义：教师关权的情况下，若获取到的试题库号不为null，则需要在返回前端前，做置空处理。
                 * 否则，会出现教师关权后学生依然可以根据返回的试题库号获取到相关信息。
                 * 教师关权 && 试题库号not null -> 执行
                 * 教师关权 && 试题库号null     -> 没必要执行
                 * 教师开权 && 试题库号not null -> 不能执行
                 * 教师开权 && 试题库号null     -> 没必要执行
                 *
                 * 把试题库的id也清空，避免出现有非法手段通过id获取到试题
                 */
                if (next.get("studentPreview").equals(false) && !next.get("questionBank").equals(null)) {
                    next.put("questionBank", null);
                    next.put("id", null);
                }

                /**
                 * 下列判断执行意义：判断返回的记录信息中，若存在有某一门考试未开始、考试正在进行中，直接移除该项数据既list中的msp
                 * 否则，会出现学生未开始考试、考试正在进行时，便在考试记录信息中出现，不合理
                 * 注：只有在考试结束时间 - 服务器当前时间 < 0 的情况下，既，考试结束的情况下，可以返回该项数据
                 */
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
                String endTime = next.get("endTime").toString();
                if (sdf.parse(endTime).getTime() > new Date().getTime()) {
                    iterator.remove();
                } else {
                    /**
                     * 将之前获取到的学生科目成绩添加到该数组对象中
                     */
                    next.put("course", classAllStudent.get(next.get("coursesName")));
                }
            }
        }
        /**
         * SerializerFeature.WriteMapNullValue 强制打印输出map中的null值
         * 试题库号 可能会存在null的情况（教师未创建试题库）
         */
        //return JSON.toJSONString(classCourseExaminationTime, SerializerFeature.WriteMapNullValue);
        /**
         * 采用直接返回List<Map>也可以实现map中存在null值返回前端，这需要该方法的返回类型为Object
         * 若为String类型的返回值，在数组对象转换时，就需要采用上一个返回方法
         */
        return classCourseExaminationTime;
    }
}
